package com.prism.e4.rcp.ui;

import java.util.Stack;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;

public class EditorService implements KeyListener, ExtendedModifyListener {

	/**
	 * Encapsulation for undo, redo
	 */
	public static class UndoRedoStack<T> {

		private Stack<T> undo;
		private Stack<T> redo;

		public UndoRedoStack() {
			undo = new Stack<T>();
			redo = new Stack<T>();
		}

		public void pushUndo(T delta) {
			undo.add(delta);
		}

		public void pushRedo(T delta) {
			redo.add(delta);
		}

		public T popUndo() {
			T res = undo.pop();
			return res;
		}

		public T popRedo() {
			T res = redo.pop();
			return res;
		}

		public void clearRedo() {
			redo.clear();
		}

		public boolean hasUndo() {
			return !undo.isEmpty();
		}

		public boolean hasRedo() {
			return !redo.isEmpty();
		}
	}

	private StyledText editor;

	private UndoRedoStack<ExtendedModifyEvent> stack;

	private boolean isUndo;

	private boolean isRedo;

	public EditorService(StyledText editor) {

		editor.addExtendedModifyListener(this);
		editor.addKeyListener(this);

		this.editor = editor;
		stack = new UndoRedoStack<ExtendedModifyEvent>();
	}

	@Override
	public void modifyText(ExtendedModifyEvent event) {
		if (isUndo) {
            stack.pushRedo(event);
        } else { // is Redo or a normal user action
            stack.pushUndo(event);
            if (!isRedo) {
                stack.clearRedo();
                // TODO Switch to treat consecutive characters as one event?
            }
        }

	}

	@Override
	public void keyPressed(KeyEvent e) {
		// Listen to CTRL+Z for Undo, to CTRL+Y or CTRL+SHIFT+Z for Redo
		// Listen to CTRL+X for Cut, to CTRL+C or for Copy, to CTRL+V for Paste
		boolean isCtrl = (e.stateMask & SWT.CTRL) > 0;
		boolean isAlt = (e.stateMask & SWT.ALT) > 0;
		boolean isShift = (e.stateMask & SWT.SHIFT) > 0;
		if (isCtrl && !isAlt) {
			if (!isShift && !isAlt && e.keyCode == 'z') {
				undo();
			} else if (!isShift && !isAlt && e.keyCode == 'y' || isShift &&
					!isAlt && e.keyCode == 'z') {
				redo();
			} else if (!isShift && !isAlt && e.keyCode == 'x') {
				cut();
			} else if (!isShift && !isAlt && e.keyCode == 'p') {
				paste();
			} else if (!isShift && !isAlt && e.keyCode == 'c') {
				copy();
			}
		}
	}

	public void copy() {
		editor.copy();
		
	}

	public void paste() {
		editor.paste();
		
	}

	public void cut() {
		editor.cut();
		
	}

	public void undo() {
        if (stack.hasUndo()) {
            isUndo = true;
            revertEvent(stack.popUndo());
            isUndo = false;
        }
    }

	public void redo() {
        if (stack.hasRedo()) {
            isRedo = true;
            revertEvent(stack.popRedo());
            isRedo = false;
        }
    }
	
	public void delete() {
		int[] array = editor.getSelectionRanges();
		editor.replaceTextRange(array[0], array[1], "");
	}
	
	public void sellectAll() {
		editor.setSelectionRange(0, editor.getCharCount());
	}
	
    private void revertEvent(ExtendedModifyEvent event) {
        editor.replaceTextRange(event.start, event.length, event.replacedText);
        // (causes the modifyText() listener method to be called)
        editor.setSelectionRange(event.start, event.replacedText.length());
    }

	@Override
	public void keyReleased(KeyEvent e) {
		// ignore
		//TODO: update other gui elements
	}
}
